@im-nobsidian/core 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +17 -0
- package/dist/index.js +356 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -340,6 +340,7 @@ declare class StateDB {
|
|
|
340
340
|
updateStatus(id: string, status: SyncStatus): void;
|
|
341
341
|
updateHash(id: string, hash: string, snapshot?: Buffer | null): void;
|
|
342
342
|
setNotionLastEdited(id: string, lastEdited: string): void;
|
|
343
|
+
setNotionParentId(id: string, parentId: string): void;
|
|
343
344
|
delete(id: string): void;
|
|
344
345
|
resolveWikilink(text: string): WikilinkEntry | null;
|
|
345
346
|
resolvePageId(pageId: string): WikilinkEntry | null;
|
|
@@ -394,6 +395,12 @@ declare class NotionClient {
|
|
|
394
395
|
updatePageProperties(pageId: string, properties: Record<string, unknown>): Promise<PageObjectResponse>;
|
|
395
396
|
getPageMarkdown(pageId: string): Promise<PageMarkdownResponse>;
|
|
396
397
|
replacePageMarkdown(pageId: string, markdown: string): Promise<PageMarkdownResponse>;
|
|
398
|
+
updatePageMarkdownPartial(pageId: string, patches: Array<{
|
|
399
|
+
oldStr: string;
|
|
400
|
+
newStr: string;
|
|
401
|
+
replaceAll?: boolean;
|
|
402
|
+
}>): Promise<PageMarkdownResponse>;
|
|
403
|
+
movePage(pageId: string, newParentId: string, newParentType: "page" | "database"): Promise<PageObjectResponse>;
|
|
397
404
|
getDatabaseSchema(databaseId: string): Promise<Record<string, {
|
|
398
405
|
id: string;
|
|
399
406
|
type: string;
|
|
@@ -542,8 +549,14 @@ declare class NotionBlockBuilder {
|
|
|
542
549
|
}
|
|
543
550
|
|
|
544
551
|
type NotionPropertyValue = Record<string, unknown>;
|
|
552
|
+
interface WikilinkResolver$1 {
|
|
553
|
+
resolve(title: string): string | null;
|
|
554
|
+
resolvePageId(pageId: string): string | null;
|
|
555
|
+
}
|
|
545
556
|
declare class PropertyMapper {
|
|
546
557
|
private schema;
|
|
558
|
+
private wikilinkResolver;
|
|
559
|
+
setWikilinkResolver(resolver: WikilinkResolver$1): void;
|
|
547
560
|
loadSchema(properties: Record<string, {
|
|
548
561
|
id: string;
|
|
549
562
|
type: string;
|
|
@@ -601,6 +614,7 @@ declare class SyncOrchestrator {
|
|
|
601
614
|
private pushCreate;
|
|
602
615
|
private pushUpdate;
|
|
603
616
|
private pushDelete;
|
|
617
|
+
private pushMove;
|
|
604
618
|
private detectRemoteChanges;
|
|
605
619
|
private pullCreate;
|
|
606
620
|
private pullUpdate;
|
|
@@ -611,6 +625,7 @@ declare class SyncOrchestrator {
|
|
|
611
625
|
private resolveParentPath;
|
|
612
626
|
private pushCreatePage;
|
|
613
627
|
private pushUpdatePage;
|
|
628
|
+
private computePatches;
|
|
614
629
|
private fetchPageMarkdown;
|
|
615
630
|
private extractParentId;
|
|
616
631
|
private cleanupInterruptedSync;
|
|
@@ -698,6 +713,8 @@ declare class BlockConverter {
|
|
|
698
713
|
private registerTocTransformer;
|
|
699
714
|
private registerBreadcrumbTransformer;
|
|
700
715
|
private registerLinkPreviewTransformer;
|
|
716
|
+
private registerPdfTransformer;
|
|
717
|
+
private registerEmbedTransformer;
|
|
701
718
|
private registerCalloutTransformer;
|
|
702
719
|
markdownToNotionBlocks(markdown: string): unknown[];
|
|
703
720
|
private toggleContents;
|
package/dist/index.js
CHANGED
|
@@ -371,6 +371,11 @@ var StateDB = class _StateDB {
|
|
|
371
371
|
"UPDATE sync_state SET notion_last_edited = ?, updated_at = datetime('now') WHERE id = ?"
|
|
372
372
|
).run(lastEdited, id);
|
|
373
373
|
}
|
|
374
|
+
setNotionParentId(id, parentId) {
|
|
375
|
+
this.db.prepare(
|
|
376
|
+
"UPDATE sync_state SET notion_parent_id = ?, updated_at = datetime('now') WHERE id = ?"
|
|
377
|
+
).run(parentId, id);
|
|
378
|
+
}
|
|
374
379
|
delete(id) {
|
|
375
380
|
this.db.prepare("DELETE FROM sync_state WHERE id = ?").run(id);
|
|
376
381
|
}
|
|
@@ -453,8 +458,13 @@ var StateDB = class _StateDB {
|
|
|
453
458
|
|
|
454
459
|
// src/notion/property-mapper.ts
|
|
455
460
|
var DATE_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2})?/;
|
|
461
|
+
var WIKILINK_REGEX = /^\[\[(.+?)(?:\|.+?)?\]\]$/;
|
|
456
462
|
var PropertyMapper = class {
|
|
457
463
|
schema = /* @__PURE__ */ new Map();
|
|
464
|
+
wikilinkResolver = null;
|
|
465
|
+
setWikilinkResolver(resolver) {
|
|
466
|
+
this.wikilinkResolver = resolver;
|
|
467
|
+
}
|
|
458
468
|
loadSchema(properties) {
|
|
459
469
|
this.schema.clear();
|
|
460
470
|
for (const [name, prop] of Object.entries(properties)) {
|
|
@@ -506,9 +516,13 @@ var PropertyMapper = class {
|
|
|
506
516
|
case "checkbox":
|
|
507
517
|
return { checkbox: Boolean(value) };
|
|
508
518
|
case "date": {
|
|
519
|
+
if (typeof value === "object" && value !== null && "start" in value) {
|
|
520
|
+
const obj = value;
|
|
521
|
+
return { date: { start: obj.start, end: obj.end ?? null } };
|
|
522
|
+
}
|
|
509
523
|
const dateStr = String(value);
|
|
510
524
|
if (DATE_REGEX.test(dateStr)) {
|
|
511
|
-
return { date: { start: dateStr } };
|
|
525
|
+
return { date: { start: dateStr, end: null } };
|
|
512
526
|
}
|
|
513
527
|
return null;
|
|
514
528
|
}
|
|
@@ -520,6 +534,54 @@ var PropertyMapper = class {
|
|
|
520
534
|
return { phone_number: String(value) };
|
|
521
535
|
case "status":
|
|
522
536
|
return { status: { name: String(value) } };
|
|
537
|
+
case "relation": {
|
|
538
|
+
const items = Array.isArray(value) ? value : [value];
|
|
539
|
+
const ids = [];
|
|
540
|
+
for (const item of items) {
|
|
541
|
+
const str = String(item);
|
|
542
|
+
const match = str.match(WIKILINK_REGEX);
|
|
543
|
+
if (match?.[1] && this.wikilinkResolver) {
|
|
544
|
+
const pageId = this.wikilinkResolver.resolve(match[1]);
|
|
545
|
+
if (pageId) ids.push({ id: pageId });
|
|
546
|
+
} else if (str.match(/^[0-9a-f-]{32,36}$/)) {
|
|
547
|
+
ids.push({ id: str });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return ids.length > 0 ? { relation: ids } : null;
|
|
551
|
+
}
|
|
552
|
+
case "people": {
|
|
553
|
+
const users = Array.isArray(value) ? value : [value];
|
|
554
|
+
const peopleArr = users.map((u) => {
|
|
555
|
+
const s = String(u);
|
|
556
|
+
if (s.match(/^[0-9a-f-]{32,36}$/)) return { object: "user", id: s };
|
|
557
|
+
return null;
|
|
558
|
+
}).filter(Boolean);
|
|
559
|
+
return peopleArr.length > 0 ? { people: peopleArr } : null;
|
|
560
|
+
}
|
|
561
|
+
case "files": {
|
|
562
|
+
const fileList = Array.isArray(value) ? value : [value];
|
|
563
|
+
const filesArr = fileList.map((f) => {
|
|
564
|
+
if (typeof f === "object" && f !== null && "url" in f) {
|
|
565
|
+
const obj = f;
|
|
566
|
+
return { type: "external", name: obj.name ?? "file", external: { url: obj.url } };
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
type: "external",
|
|
570
|
+
name: String(f),
|
|
571
|
+
external: { url: String(f) }
|
|
572
|
+
};
|
|
573
|
+
});
|
|
574
|
+
return { files: filesArr };
|
|
575
|
+
}
|
|
576
|
+
case "created_time":
|
|
577
|
+
case "last_edited_time":
|
|
578
|
+
case "created_by":
|
|
579
|
+
case "last_edited_by":
|
|
580
|
+
case "formula":
|
|
581
|
+
case "rollup":
|
|
582
|
+
case "unique_id":
|
|
583
|
+
case "verification":
|
|
584
|
+
return null;
|
|
523
585
|
default:
|
|
524
586
|
return { rich_text: [{ text: { content: String(value) } }] };
|
|
525
587
|
}
|
|
@@ -538,7 +600,7 @@ var PropertyMapper = class {
|
|
|
538
600
|
}
|
|
539
601
|
if (typeof value === "string") {
|
|
540
602
|
if (DATE_REGEX.test(value)) {
|
|
541
|
-
return { date: { start: value } };
|
|
603
|
+
return { date: { start: value, end: null } };
|
|
542
604
|
}
|
|
543
605
|
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
544
606
|
return { url: value };
|
|
@@ -565,8 +627,12 @@ var PropertyMapper = class {
|
|
|
565
627
|
}
|
|
566
628
|
case "checkbox":
|
|
567
629
|
return prop.checkbox;
|
|
568
|
-
case "date":
|
|
569
|
-
|
|
630
|
+
case "date": {
|
|
631
|
+
const dateObj = prop.date;
|
|
632
|
+
if (!dateObj) return null;
|
|
633
|
+
if (dateObj.end) return { start: dateObj.start, end: dateObj.end };
|
|
634
|
+
return dateObj.start;
|
|
635
|
+
}
|
|
570
636
|
case "url":
|
|
571
637
|
return prop.url;
|
|
572
638
|
case "email":
|
|
@@ -599,7 +665,14 @@ var PropertyMapper = class {
|
|
|
599
665
|
}
|
|
600
666
|
case "relation": {
|
|
601
667
|
const rel = prop.relation;
|
|
602
|
-
|
|
668
|
+
if (!rel || rel.length === 0) return [];
|
|
669
|
+
return rel.map((r) => {
|
|
670
|
+
if (this.wikilinkResolver) {
|
|
671
|
+
const title = this.wikilinkResolver.resolvePageId(r.id);
|
|
672
|
+
if (title) return `[[${title}]]`;
|
|
673
|
+
}
|
|
674
|
+
return r.id;
|
|
675
|
+
});
|
|
603
676
|
}
|
|
604
677
|
case "rollup": {
|
|
605
678
|
const rollup = prop.rollup;
|
|
@@ -701,6 +774,32 @@ var NotionClient = class {
|
|
|
701
774
|
})
|
|
702
775
|
);
|
|
703
776
|
}
|
|
777
|
+
async updatePageMarkdownPartial(pageId, patches) {
|
|
778
|
+
return this.withRateLimit(
|
|
779
|
+
() => this.client.pages.updateMarkdown({
|
|
780
|
+
page_id: pageId,
|
|
781
|
+
type: "update_content",
|
|
782
|
+
update_content: {
|
|
783
|
+
content_updates: patches.map((p) => ({
|
|
784
|
+
old_str: p.oldStr,
|
|
785
|
+
new_str: p.newStr,
|
|
786
|
+
replace_all_matches: p.replaceAll ?? false
|
|
787
|
+
})),
|
|
788
|
+
allow_deleting_content: true
|
|
789
|
+
}
|
|
790
|
+
})
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
// ─── Page Move ───
|
|
794
|
+
async movePage(pageId, newParentId, newParentType) {
|
|
795
|
+
const parent = newParentType === "database" ? { database_id: newParentId } : { page_id: newParentId };
|
|
796
|
+
return this.withRateLimit(
|
|
797
|
+
() => this.client.pages.update({
|
|
798
|
+
page_id: pageId,
|
|
799
|
+
parent
|
|
800
|
+
})
|
|
801
|
+
);
|
|
802
|
+
}
|
|
704
803
|
// ─── Database / DataSource ───
|
|
705
804
|
async getDatabaseSchema(databaseId) {
|
|
706
805
|
const db = await this.withRateLimit(
|
|
@@ -1398,7 +1497,7 @@ var FrontmatterExtractor = class {
|
|
|
1398
1497
|
};
|
|
1399
1498
|
|
|
1400
1499
|
// src/converter/pre-processors/wikilink.ts
|
|
1401
|
-
var
|
|
1500
|
+
var WIKILINK_REGEX2 = /(?<!!)\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g;
|
|
1402
1501
|
var WikilinkResolver = class {
|
|
1403
1502
|
constructor(resolve) {
|
|
1404
1503
|
this.resolve = resolve;
|
|
@@ -1412,7 +1511,7 @@ var WikilinkResolver = class {
|
|
|
1412
1511
|
}
|
|
1413
1512
|
const preserveMarkers = input.metadata.preserveMarkers ? [...input.metadata.preserveMarkers] : [];
|
|
1414
1513
|
const resolved = input.content.replace(
|
|
1415
|
-
|
|
1514
|
+
WIKILINK_REGEX2,
|
|
1416
1515
|
(_match, target, display) => {
|
|
1417
1516
|
const label = display ?? target;
|
|
1418
1517
|
if (this.resolve) {
|
|
@@ -1784,13 +1883,50 @@ var FrontmatterGenerator = class {
|
|
|
1784
1883
|
if (!properties || Object.keys(properties).length === 0) {
|
|
1785
1884
|
return { content: input.content, metadata: input.metadata };
|
|
1786
1885
|
}
|
|
1787
|
-
const
|
|
1886
|
+
const normalized = normalizeProperties(properties);
|
|
1887
|
+
const content = matter.stringify(input.content, normalized);
|
|
1788
1888
|
return {
|
|
1789
1889
|
content,
|
|
1790
1890
|
metadata: input.metadata
|
|
1791
1891
|
};
|
|
1792
1892
|
}
|
|
1793
1893
|
};
|
|
1894
|
+
function normalizeProperties(props) {
|
|
1895
|
+
const result = {};
|
|
1896
|
+
for (const [key, value] of Object.entries(props)) {
|
|
1897
|
+
const normalized = normalizeValue(value);
|
|
1898
|
+
if (normalized !== void 0) {
|
|
1899
|
+
result[key] = normalized;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return result;
|
|
1903
|
+
}
|
|
1904
|
+
function normalizeValue(value) {
|
|
1905
|
+
if (typeof value === "string") {
|
|
1906
|
+
return normalizeDate(value);
|
|
1907
|
+
}
|
|
1908
|
+
if (Array.isArray(value)) {
|
|
1909
|
+
if (value.length === 0) return void 0;
|
|
1910
|
+
return value;
|
|
1911
|
+
}
|
|
1912
|
+
if (typeof value === "object" && value !== null && "start" in value) {
|
|
1913
|
+
const dateObj = value;
|
|
1914
|
+
const normalized = {
|
|
1915
|
+
start: normalizeDate(dateObj.start),
|
|
1916
|
+
...dateObj.end ? { end: normalizeDate(dateObj.end) } : {}
|
|
1917
|
+
};
|
|
1918
|
+
return normalized;
|
|
1919
|
+
}
|
|
1920
|
+
return value;
|
|
1921
|
+
}
|
|
1922
|
+
var MIDNIGHT_RE = /T00:00:00(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})/;
|
|
1923
|
+
function normalizeDate(dateStr) {
|
|
1924
|
+
if (typeof dateStr !== "string") return dateStr;
|
|
1925
|
+
if (MIDNIGHT_RE.test(dateStr)) {
|
|
1926
|
+
return dateStr.split("T")[0] ?? dateStr;
|
|
1927
|
+
}
|
|
1928
|
+
return dateStr;
|
|
1929
|
+
}
|
|
1794
1930
|
|
|
1795
1931
|
// src/converter/post-processors/local-image-restorer.ts
|
|
1796
1932
|
var LOCAL_IMAGE_MARKER_REGEX = />\s*📎\s*[^\n]*\n>\s*%%\s*im-nobsidian:local-image:([^\s]+)\s*%%/g;
|
|
@@ -2029,6 +2165,8 @@ var BlockConverter = class {
|
|
|
2029
2165
|
this.registerTocTransformer();
|
|
2030
2166
|
this.registerBreadcrumbTransformer();
|
|
2031
2167
|
this.registerLinkPreviewTransformer();
|
|
2168
|
+
this.registerPdfTransformer();
|
|
2169
|
+
this.registerEmbedTransformer();
|
|
2032
2170
|
this.registerCalloutTransformer();
|
|
2033
2171
|
this.registerToggleHeadingTransformers();
|
|
2034
2172
|
}
|
|
@@ -2201,6 +2339,24 @@ ${COLUMN_LIST_END}`;
|
|
|
2201
2339
|
return `[${url}](${url})`;
|
|
2202
2340
|
});
|
|
2203
2341
|
}
|
|
2342
|
+
registerPdfTransformer() {
|
|
2343
|
+
if (!this.n2m) return;
|
|
2344
|
+
this.n2m.setCustomTransformer("pdf", async (block) => {
|
|
2345
|
+
const b = block;
|
|
2346
|
+
const url = b.pdf?.type === "file" ? b.pdf.file?.url ?? "" : b.pdf?.external?.url ?? "";
|
|
2347
|
+
const caption = b.pdf?.caption ? richTextToMarkdown(b.pdf.caption) : "";
|
|
2348
|
+
return `[\u{1F4C4} ${caption || "PDF"}](${url})`;
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
registerEmbedTransformer() {
|
|
2352
|
+
if (!this.n2m) return;
|
|
2353
|
+
this.n2m.setCustomTransformer("embed", async (block) => {
|
|
2354
|
+
const b = block;
|
|
2355
|
+
const url = b.embed?.url ?? "";
|
|
2356
|
+
const caption = b.embed?.caption ? richTextToMarkdown(b.embed.caption) : "";
|
|
2357
|
+
return `[${caption || url}](${url})`;
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2204
2360
|
registerCalloutTransformer() {
|
|
2205
2361
|
if (!this.n2m) return;
|
|
2206
2362
|
const n2m = this.n2m;
|
|
@@ -2744,7 +2900,13 @@ var NOTION_TOGGLE_RE = /<details>\s*<summary>([\s\S]*?)<\/summary>([\s\S]*?)<\/d
|
|
|
2744
2900
|
var NOTION_PAGE_MENTION_RE = /<mention-page id="([^"]+)">([\s\S]*?)<\/mention-page>/g;
|
|
2745
2901
|
var NOTION_USER_MENTION_RE = /<mention-user id="[^"]*">([^<]*)<\/mention-user>/g;
|
|
2746
2902
|
var NOTION_DATE_MENTION_RE = /<mention-date start="([^"]*)"(?: end="([^"]*)")?[^>]*\/>/g;
|
|
2747
|
-
var NOTION_UNKNOWN_RE = /<unknown id="[^"]*"[^>]
|
|
2903
|
+
var NOTION_UNKNOWN_RE = /<unknown id="([^"]*)"([^>]*)\/>/g;
|
|
2904
|
+
var NOTION_AUDIO_RE = /<audio src="([^"]*)">([\s\S]*?)<\/audio>/g;
|
|
2905
|
+
var NOTION_VIDEO_RE = /<video src="([^"]*)">([\s\S]*?)<\/video>/g;
|
|
2906
|
+
var NOTION_PDF_RE = /<pdf src="([^"]*)">([\s\S]*?)<\/pdf>/g;
|
|
2907
|
+
var NOTION_FILE_RE = /<file src="([^"]*)">([\s\S]*?)<\/file>/g;
|
|
2908
|
+
var NOTION_TAB_RE = /<tab title="([^"]*)">([\s\S]*?)<\/tab>/g;
|
|
2909
|
+
var NOTION_UNDERLINE_RE = /<span underline="true">([\s\S]*?)<\/span>/g;
|
|
2748
2910
|
var TOGGLE_START2 = "%%im-nobsidian:toggle:start%%";
|
|
2749
2911
|
var TOGGLE_END2 = "%%im-nobsidian:toggle:end%%";
|
|
2750
2912
|
function notionEnhancedToObsidian(enhanced) {
|
|
@@ -2755,10 +2917,13 @@ function notionEnhancedToObsidian(enhanced) {
|
|
|
2755
2917
|
result = convertPageLinks(result);
|
|
2756
2918
|
result = convertUserMentions(result);
|
|
2757
2919
|
result = convertDateMentions(result);
|
|
2758
|
-
result =
|
|
2920
|
+
result = convertMediaTags(result);
|
|
2921
|
+
result = convertTabBlocks(result);
|
|
2922
|
+
result = preserveUnknownBlocks(result);
|
|
2759
2923
|
result = convertNotionMath(result);
|
|
2760
2924
|
result = convertNotionTables(result);
|
|
2761
2925
|
result = convertColorSpans(result);
|
|
2926
|
+
result = convertUnderlineSpans(result);
|
|
2762
2927
|
result = removeEmptyBlocks(result);
|
|
2763
2928
|
result = unescapePipes(result);
|
|
2764
2929
|
return result;
|
|
@@ -2766,7 +2931,12 @@ function notionEnhancedToObsidian(enhanced) {
|
|
|
2766
2931
|
function obsidianToNotionEnhanced(obsidian) {
|
|
2767
2932
|
let result = obsidian;
|
|
2768
2933
|
result = convertTogglesToHtml(result);
|
|
2934
|
+
result = restoreTabBlocks(result);
|
|
2769
2935
|
result = convertObsidianCallouts(result);
|
|
2936
|
+
result = restoreMediaTags(result);
|
|
2937
|
+
result = restoreUnknownBlocks(result);
|
|
2938
|
+
result = restoreColorSpans(result);
|
|
2939
|
+
result = restoreUnderlineSpans(result);
|
|
2770
2940
|
return result;
|
|
2771
2941
|
}
|
|
2772
2942
|
function convertToggles(content) {
|
|
@@ -2811,8 +2981,46 @@ function convertDateMentions(content) {
|
|
|
2811
2981
|
return end ? `${start} \u2192 ${end}` : start;
|
|
2812
2982
|
});
|
|
2813
2983
|
}
|
|
2814
|
-
function
|
|
2815
|
-
return content.replace(NOTION_UNKNOWN_RE,
|
|
2984
|
+
function preserveUnknownBlocks(content) {
|
|
2985
|
+
return content.replace(NOTION_UNKNOWN_RE, (_match, id, attrs) => {
|
|
2986
|
+
const typeMatch = /type="([^"]*)"/.exec(attrs);
|
|
2987
|
+
const altMatch = /alt="([^"]*)"/.exec(attrs);
|
|
2988
|
+
const blockType = altMatch?.[1] ?? typeMatch?.[1] ?? "unknown";
|
|
2989
|
+
return `%%im-nobsidian:unknown:id=${id}&type=${blockType}%%`;
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
function convertMediaTags(content) {
|
|
2993
|
+
let result = content;
|
|
2994
|
+
result = result.replace(NOTION_AUDIO_RE, (_match, src, caption) => {
|
|
2995
|
+
const cap = caption.trim();
|
|
2996
|
+
return cap ? `[\u{1F50A} ${cap}](${src})` : `[\u{1F50A} audio](${src})`;
|
|
2997
|
+
});
|
|
2998
|
+
result = result.replace(NOTION_VIDEO_RE, (_match, src, caption) => {
|
|
2999
|
+
const cap = caption.trim();
|
|
3000
|
+
return cap ? `[\u{1F3AC} ${cap}](${src})` : `[\u{1F3AC} video](${src})`;
|
|
3001
|
+
});
|
|
3002
|
+
result = result.replace(NOTION_PDF_RE, (_match, src, caption) => {
|
|
3003
|
+
const cap = caption.trim();
|
|
3004
|
+
return cap ? `[\u{1F4C4} ${cap}](${src})` : `[\u{1F4C4} pdf](${src})`;
|
|
3005
|
+
});
|
|
3006
|
+
result = result.replace(NOTION_FILE_RE, (_match, src, caption) => {
|
|
3007
|
+
const cap = caption.trim();
|
|
3008
|
+
return cap ? `[\u{1F4CE} ${cap}](${src})` : `[\u{1F4CE} file](${src})`;
|
|
3009
|
+
});
|
|
3010
|
+
return result;
|
|
3011
|
+
}
|
|
3012
|
+
function convertTabBlocks(content) {
|
|
3013
|
+
return content.replace(NOTION_TAB_RE, (_match, title, body) => {
|
|
3014
|
+
const trimmed = body.trim();
|
|
3015
|
+
const indented = trimmed.split("\n").map((line) => `> ${line}`).join("\n");
|
|
3016
|
+
return `> [!tab] ${title}
|
|
3017
|
+
${indented}`;
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
function convertUnderlineSpans(content) {
|
|
3021
|
+
return content.replace(NOTION_UNDERLINE_RE, (_match, text) => {
|
|
3022
|
+
return `%%im-nobsidian:underline%%${text}%%/underline%%`;
|
|
3023
|
+
});
|
|
2816
3024
|
}
|
|
2817
3025
|
function convertTogglesToHtml(content) {
|
|
2818
3026
|
const startRe = new RegExp(escapeRegex3(TOGGLE_START2), "g");
|
|
@@ -2922,7 +3130,10 @@ function convertPageLinks(content) {
|
|
|
2922
3130
|
});
|
|
2923
3131
|
}
|
|
2924
3132
|
function convertColorSpans(content) {
|
|
2925
|
-
return content.replace(
|
|
3133
|
+
return content.replace(
|
|
3134
|
+
NOTION_COLOR_SPAN_RE,
|
|
3135
|
+
(_match, color, text) => `%%im-nobsidian:color:${color}%%${text}%%/color%%`
|
|
3136
|
+
);
|
|
2926
3137
|
}
|
|
2927
3138
|
function removeEmptyBlocks(content) {
|
|
2928
3139
|
return content.replace(NOTION_EMPTY_BLOCK_RE, "\n");
|
|
@@ -2969,6 +3180,51 @@ function convertNotionTables(content) {
|
|
|
2969
3180
|
return lines.join("\n");
|
|
2970
3181
|
});
|
|
2971
3182
|
}
|
|
3183
|
+
var OBSIDIAN_MEDIA_AUDIO_RE = /\[🔊\s*([^\]]*)\]\(([^)]+)\)/g;
|
|
3184
|
+
var OBSIDIAN_MEDIA_VIDEO_RE = /\[🎬\s*([^\]]*)\]\(([^)]+)\)/g;
|
|
3185
|
+
var OBSIDIAN_MEDIA_PDF_RE = /\[📄\s*([^\]]*)\]\(([^)]+)\)/g;
|
|
3186
|
+
var OBSIDIAN_MEDIA_FILE_RE = /\[📎\s*([^\]]*)\]\(([^)]+)\)/g;
|
|
3187
|
+
function restoreMediaTags(content) {
|
|
3188
|
+
let result = content;
|
|
3189
|
+
result = result.replace(OBSIDIAN_MEDIA_AUDIO_RE, (_match, caption, src) => {
|
|
3190
|
+
return `<audio src="${src}">${caption}</audio>`;
|
|
3191
|
+
});
|
|
3192
|
+
result = result.replace(OBSIDIAN_MEDIA_VIDEO_RE, (_match, caption, src) => {
|
|
3193
|
+
return `<video src="${src}">${caption}</video>`;
|
|
3194
|
+
});
|
|
3195
|
+
result = result.replace(OBSIDIAN_MEDIA_PDF_RE, (_match, caption, src) => {
|
|
3196
|
+
return `<pdf src="${src}">${caption}</pdf>`;
|
|
3197
|
+
});
|
|
3198
|
+
result = result.replace(OBSIDIAN_MEDIA_FILE_RE, (_match, caption, src) => {
|
|
3199
|
+
return `<file src="${src}">${caption}</file>`;
|
|
3200
|
+
});
|
|
3201
|
+
return result;
|
|
3202
|
+
}
|
|
3203
|
+
var OBSIDIAN_TAB_RE = /^> \[!tab\]\s*(.+)\n((?:> .*\n?)*)/gm;
|
|
3204
|
+
function restoreTabBlocks(content) {
|
|
3205
|
+
return content.replace(OBSIDIAN_TAB_RE, (_match, title, body) => {
|
|
3206
|
+
const unquoted = body.split("\n").map((line) => line.replace(/^> /, "")).join("\n").trim();
|
|
3207
|
+
return `<tab title="${title}">${unquoted}</tab>`;
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
var OBSIDIAN_UNKNOWN_RE = /%%im-nobsidian:unknown:id=([^&]+)&type=([^%]+)%%/g;
|
|
3211
|
+
function restoreUnknownBlocks(content) {
|
|
3212
|
+
return content.replace(OBSIDIAN_UNKNOWN_RE, (_match, id, type) => {
|
|
3213
|
+
return `<unknown id="${id}" type="${type}"/>`;
|
|
3214
|
+
});
|
|
3215
|
+
}
|
|
3216
|
+
var OBSIDIAN_COLOR_RE = /%%im-nobsidian:color:([^%]+)%%([\s\S]*?)%%\/color%%/g;
|
|
3217
|
+
function restoreColorSpans(content) {
|
|
3218
|
+
return content.replace(OBSIDIAN_COLOR_RE, (_match, color, text) => {
|
|
3219
|
+
return `<span color="${color}">${text}</span>`;
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
3222
|
+
var OBSIDIAN_UNDERLINE_RE = /%%im-nobsidian:underline%%([\s\S]*?)%%\/underline%%/g;
|
|
3223
|
+
function restoreUnderlineSpans(content) {
|
|
3224
|
+
return content.replace(OBSIDIAN_UNDERLINE_RE, (_match, text) => {
|
|
3225
|
+
return `<span underline="true">${text}</span>`;
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
2972
3228
|
function unescapePipes(content) {
|
|
2973
3229
|
const lines = content.split("\n");
|
|
2974
3230
|
const result = [];
|
|
@@ -3003,6 +3259,10 @@ var SyncOrchestrator = class {
|
|
|
3003
3259
|
this.blockConverter = new BlockConverter();
|
|
3004
3260
|
this.imageHandler = new ImageHandler(vaultFs, config.paths.attachments, notionClient);
|
|
3005
3261
|
this.propertyMapper = new PropertyMapper();
|
|
3262
|
+
this.propertyMapper.setWikilinkResolver({
|
|
3263
|
+
resolve: (title) => stateDb.resolveWikilink(title)?.notionPageId ?? null,
|
|
3264
|
+
resolvePageId: (pageId) => stateDb.resolvePageId(pageId)?.title ?? null
|
|
3265
|
+
});
|
|
3006
3266
|
this.blockConverter.initNotionToMd(this.notionClient.getInternalClient());
|
|
3007
3267
|
}
|
|
3008
3268
|
config;
|
|
@@ -3074,10 +3334,13 @@ var SyncOrchestrator = class {
|
|
|
3074
3334
|
created++;
|
|
3075
3335
|
break;
|
|
3076
3336
|
case "modified":
|
|
3077
|
-
case "moved":
|
|
3078
3337
|
await this.pushUpdate(change.path);
|
|
3079
3338
|
updated++;
|
|
3080
3339
|
break;
|
|
3340
|
+
case "moved":
|
|
3341
|
+
await this.pushMove(change.path);
|
|
3342
|
+
updated++;
|
|
3343
|
+
break;
|
|
3081
3344
|
case "deleted":
|
|
3082
3345
|
await this.pushDelete(change.path);
|
|
3083
3346
|
deleted++;
|
|
@@ -3254,13 +3517,20 @@ var SyncOrchestrator = class {
|
|
|
3254
3517
|
localContent = await this.vaultFs.readFile(record.obsidianPath);
|
|
3255
3518
|
} catch {
|
|
3256
3519
|
}
|
|
3520
|
+
let remoteContent = "";
|
|
3521
|
+
if (record.notionPageId) {
|
|
3522
|
+
try {
|
|
3523
|
+
remoteContent = await this.fetchPageMarkdown(record.notionPageId);
|
|
3524
|
+
} catch {
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3257
3527
|
conflicts.push({
|
|
3258
3528
|
syncRecord: record,
|
|
3259
3529
|
localChange,
|
|
3260
3530
|
remoteChange,
|
|
3261
3531
|
baseContent: record.baseSnapshot?.toString("utf-8") ?? null,
|
|
3262
3532
|
localContent,
|
|
3263
|
-
remoteContent
|
|
3533
|
+
remoteContent
|
|
3264
3534
|
});
|
|
3265
3535
|
}
|
|
3266
3536
|
return conflicts;
|
|
@@ -3350,7 +3620,7 @@ var SyncOrchestrator = class {
|
|
|
3350
3620
|
filePath: path,
|
|
3351
3621
|
parentMode: this.config.notion.parentMode
|
|
3352
3622
|
});
|
|
3353
|
-
await this.pushUpdatePage(record.notionPageId, conversionResult.content);
|
|
3623
|
+
await this.pushUpdatePage(record.notionPageId, conversionResult.content, record.baseSnapshot);
|
|
3354
3624
|
await this.imageHandler.uploadAndAppendImages(record.notionPageId, conversionResult.images);
|
|
3355
3625
|
let propsToUpdate = conversionResult.properties;
|
|
3356
3626
|
if (this.isDatabaseMode && propsToUpdate && Object.keys(propsToUpdate).length > 0) {
|
|
@@ -3388,6 +3658,22 @@ var SyncOrchestrator = class {
|
|
|
3388
3658
|
this.stateDb.updateStatus(record.id, "pending");
|
|
3389
3659
|
}
|
|
3390
3660
|
}
|
|
3661
|
+
async pushMove(path) {
|
|
3662
|
+
const record = this.stateDb.getByPath(path);
|
|
3663
|
+
if (!record?.notionPageId) return;
|
|
3664
|
+
const newParentId = await this.resolveNotionParent(path);
|
|
3665
|
+
const currentParentId = record.notionParentId;
|
|
3666
|
+
if (currentParentId && !notionIdsEqual(newParentId, currentParentId)) {
|
|
3667
|
+
try {
|
|
3668
|
+
const parentType = this.isDatabaseMode ? "database" : "page";
|
|
3669
|
+
await this.notionClient.movePage(record.notionPageId, newParentId, parentType);
|
|
3670
|
+
this.stateDb.setNotionParentId(record.id, newParentId);
|
|
3671
|
+
} catch {
|
|
3672
|
+
getLogger().warn(`[Im-Nobsidian] Move API \uC2E4\uD328 \u2014 "${path}" \uB0B4\uC6A9\uB9CC \uC5C5\uB370\uC774\uD2B8\uD569\uB2C8\uB2E4`);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
await this.pushUpdate(path);
|
|
3676
|
+
}
|
|
3391
3677
|
async detectRemoteChanges() {
|
|
3392
3678
|
const changes = [];
|
|
3393
3679
|
const lastPull = this.stateDb.getMeta("last_pull_at");
|
|
@@ -3723,10 +4009,18 @@ var SyncOrchestrator = class {
|
|
|
3723
4009
|
}
|
|
3724
4010
|
return page;
|
|
3725
4011
|
}
|
|
3726
|
-
async pushUpdatePage(pageId, markdownContent) {
|
|
4012
|
+
async pushUpdatePage(pageId, markdownContent, baseSnapshot) {
|
|
3727
4013
|
if (this.config.conversion.preferMarkdownApi !== false) {
|
|
3728
4014
|
try {
|
|
3729
4015
|
const enhanced = obsidianToNotionEnhanced(markdownContent);
|
|
4016
|
+
if (baseSnapshot) {
|
|
4017
|
+
const baseEnhanced = obsidianToNotionEnhanced(baseSnapshot.toString("utf-8"));
|
|
4018
|
+
const patches = this.computePatches(baseEnhanced, enhanced);
|
|
4019
|
+
if (patches.length > 0 && patches.length <= 20) {
|
|
4020
|
+
await this.notionClient.updatePageMarkdownPartial(pageId, patches);
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
3730
4024
|
await this.notionClient.replacePageMarkdown(pageId, enhanced);
|
|
3731
4025
|
return;
|
|
3732
4026
|
} catch {
|
|
@@ -3749,6 +4043,51 @@ var SyncOrchestrator = class {
|
|
|
3749
4043
|
})
|
|
3750
4044
|
);
|
|
3751
4045
|
}
|
|
4046
|
+
computePatches(oldContent, newContent) {
|
|
4047
|
+
const oldLines = oldContent.split("\n");
|
|
4048
|
+
const newLines = newContent.split("\n");
|
|
4049
|
+
const patches = [];
|
|
4050
|
+
let i = 0;
|
|
4051
|
+
let j = 0;
|
|
4052
|
+
while (i < oldLines.length && j < newLines.length) {
|
|
4053
|
+
if (oldLines[i] === newLines[j]) {
|
|
4054
|
+
i++;
|
|
4055
|
+
j++;
|
|
4056
|
+
continue;
|
|
4057
|
+
}
|
|
4058
|
+
let oldEnd = i + 1;
|
|
4059
|
+
let newEnd = j + 1;
|
|
4060
|
+
const lookahead = Math.min(10, oldLines.length - i, newLines.length - j);
|
|
4061
|
+
for (let k = 1; k <= lookahead; k++) {
|
|
4062
|
+
if (i + k < oldLines.length && newLines[j] === oldLines[i + k]) {
|
|
4063
|
+
oldEnd = i + k;
|
|
4064
|
+
newEnd = j;
|
|
4065
|
+
break;
|
|
4066
|
+
}
|
|
4067
|
+
if (j + k < newLines.length && oldLines[i] === newLines[j + k]) {
|
|
4068
|
+
oldEnd = i;
|
|
4069
|
+
newEnd = j + k;
|
|
4070
|
+
break;
|
|
4071
|
+
}
|
|
4072
|
+
oldEnd = i + k;
|
|
4073
|
+
newEnd = j + k;
|
|
4074
|
+
}
|
|
4075
|
+
const oldChunk = oldLines.slice(i, oldEnd).join("\n");
|
|
4076
|
+
const newChunk = newLines.slice(j, newEnd).join("\n");
|
|
4077
|
+
if (oldChunk || newChunk) {
|
|
4078
|
+
patches.push({ oldStr: oldChunk, newStr: newChunk });
|
|
4079
|
+
}
|
|
4080
|
+
i = oldEnd;
|
|
4081
|
+
j = newEnd;
|
|
4082
|
+
}
|
|
4083
|
+
if (i < oldLines.length || j < newLines.length) {
|
|
4084
|
+
patches.push({
|
|
4085
|
+
oldStr: oldLines.slice(i).join("\n"),
|
|
4086
|
+
newStr: newLines.slice(j).join("\n")
|
|
4087
|
+
});
|
|
4088
|
+
}
|
|
4089
|
+
return patches;
|
|
4090
|
+
}
|
|
3752
4091
|
async fetchPageMarkdown(pageId) {
|
|
3753
4092
|
if (this.config.conversion.preferMarkdownApi !== false) {
|
|
3754
4093
|
try {
|